#include "OpenGLRenderer.h"
// Include input and output operations
#include <iostream>
#include <sstream>
#include <vector>
#include <algorithm>

glm::mat4 projectionMatrix; // Store the projection matrix
glm::mat4 viewMatrix; // Store the view matrix
glm::mat4 modelMatrix; // Store the model matrix
int projectionMatrixLocation;
int viewMatrixLocation;
int modelMatrixLocation;

float	perspectiveMatrix[16];
const float fFrustumScale = 1.0f;
 
OpenGLRenderer::OpenGLRenderer(void)
{
	_vertexShader	= std::auto_ptr<milo::Shader> (new milo::Shader( GL_VERTEX_SHADER ));
	_fragmentShader = std::auto_ptr<milo::Shader> (new milo::Shader( GL_FRAGMENT_SHADER ));
	_program = std::auto_ptr<milo::Program> (new milo::Program());
}

OpenGLRenderer::~OpenGLRenderer(void)
{
}

void OpenGLRenderer::initializeProgram()
{
	const std::string strMatrixPerspectiveShader(
		"#version 330\n"
		"layout(location = 0) in vec4 position;\n"
		//"layout(location = 1) in vec4 color;\n"
		"smooth out vec4 theColor;\n"
		"uniform vec2 offset;\n"
		"uniform mat4 perspectiveMatrix;\n"
		"void main()\n"
		"{\n"
		"	vec4 cameraPos = position + vec4(offset.x, offset.y, 0.0, 0.0);\n"
		"	gl_Position = perspectiveMatrix * cameraPos;\n"
		//	theColor = color;\n"
		"}\n"
	);

	const std::string strVertexShader(
		"#version 330\n"
		"layout(location = 0) in vec4 position;\n"
		"uniform mat4 projectionMatrix;\n"  
		"uniform mat4 viewMatrix;\n" 
		"uniform mat4 modelMatrix;\n"  
		"void main()\n"
		"{\n"
		//"	gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(in_Position, 1.0);\n"
		"   gl_Position = position;\n"
		"}\n"
	);

	const std::string strColors(
		"#version 330\n"
		//"smooth in vec4 theColor;\n"
		"out vec4 outputColor;\n"
		"void main()\n"
		"{\n"
		//"	outputColor = theColor;\n"
		"	outputColor = vec4(.0f, .0f, 1.0f, 1.0f);\n"
		"}\n"
	);

	const std::string strFragmentShader(
		"#version 330\n"
		"out vec4 outputColor;\n"
		"void main()\n" 
		"{\n"
		"	float lerpValue = gl_FragCoord.y / 500.0f;\n"
		"   outputColor = mix(vec4(1.0f, 1.0f, 1.0f, 1.0f),\n"
        "vec4(0.2f, 0.2f, 0.2f, 1.0f), lerpValue);\n"
		"}\n"
	);
	std::vector<GLuint> shaderList(2);
	_vertexShader->loadFromString( strMatrixPerspectiveShader );
	_fragmentShader->loadFromString( strColors );
	shaderList[ 0 ] = _vertexShader->getId();
	shaderList[ 1 ] = _fragmentShader->getId();
	
	_program->createProgram( shaderList );

	_offsetUniform = glGetUniformLocation(_program->getId(), "offset");
	_perspectiveMatrixUnif = glGetUniformLocation(_program->getId(), "perspectiveMatrix");
	float fzNear = 0.5f; float fzFar = 3.0f;

	memset(perspectiveMatrix, 0, sizeof(float) * 16);

	perspectiveMatrix[0] = fFrustumScale;
	perspectiveMatrix[5] = fFrustumScale;
	perspectiveMatrix[10] = (fzFar + fzNear) / (fzNear - fzFar);
	perspectiveMatrix[14] = (2 * fzFar * fzNear) / (fzNear - fzFar);
	perspectiveMatrix[11] = -1.0f;

	glUseProgram( _program->getId() );
	glUniformMatrix4fv(_perspectiveMatrixUnif, 1, GL_FALSE, perspectiveMatrix);
	glUseProgram(0);
}

void OpenGLRenderer::init(Data* d)
{
	initializeProgram();

	std::vector<float> f;
	_node = milo::Mesh(f);

	glEnable(GL_CULL_FACE);
	glCullFace(GL_BACK);
	glFrontFace(GL_CW);
	
	//projectionMatrix = glm::perspective(60.0f, (float)windowWidth/(float)windowHeight, 0.1f, 100.0f);
	//viewMatrix = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -5.f)); // Create our view matrix which will translate us back 5 units
	//modelMatrix = glm::scale(glm::mat4(1.0f), glm::vec3(1.0f)); // Create our model matrix which will halve the size of our model
	//int projectionMatrixLocation = glGetUniformLocation(_vertexShader.getId(), "projectionMatrix"); // Get the location of our projection matrix in the shader  
	//int viewMatrixLocation = glGetUniformLocation(_vertexShader.getId(), "viewMatrix"); // Get the location of our view matrix in the shader  
	//int modelMatrixLocation = glGetUniformLocation(_vertexShader.getId(), "modelMatrix"); // Get the location of our model matrix in the shader  
}

DWORD previous = 0;
void refreshFPS()
{
	static int frames = 0;
	static DWORD start = GetTickCount();
	++frames;
	start = GetTickCount();
	if (start - previous >= 1000){
		std::cout << "FPS: " << frames << std::endl;
		previous = start;
		frames = 0;
	}
}


void OpenGLRenderer::display()
{
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	glClear(GL_COLOR_BUFFER_BIT);
	
	glUseProgram( _program->getId() );

	glUniform2f(_offsetUniform, 0.5f, 0.5f);
	//glUniformMatrix4fv(projectionMatrixLocation, 1, GL_FALSE, &projectionMatrix[0][0]); // Send our projection matrix to the shader
	//glUniformMatrix4fv(viewMatrixLocation, 1, GL_FALSE, &viewMatrix[0][0]); // Send our view matrix to the shader
	//glUniformMatrix4fv(modelMatrixLocation, 1, GL_FALSE, &modelMatrix[0][0]); // Send our model matrix to the shader
	
	_node.paint();

	glUseProgram(0);
	
	glutSwapBuffers();
	glutPostRedisplay(); // To repaint the window

	refreshFPS(); // Refresh the FPS
}

void OpenGLRenderer::reshape(int w, int h)
{
	glViewport(0, 0, (GLsizei) w, (GLsizei) h);

	perspectiveMatrix[0] = fFrustumScale / (w / (float)h);
	perspectiveMatrix[5] = fFrustumScale;

	glUseProgram( _program->getId() );
	glUniformMatrix4fv(_perspectiveMatrixUnif, 1, GL_FALSE, perspectiveMatrix);
	glUseProgram(0);
}

void OpenGLRenderer::keyboard(unsigned char key, int x, int y)
{
	switch (key)
	{
	  case 27:
		  glutLeaveMainLoop();
		  return;
	}
}